/*
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef FLATBUFFERS_MINIREFLECT_H_
#define FLATBUFFERS_MINIREFLECT_H_

#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/util.h"

namespace flatbuffers {

// Utilities that can be used with the "mini reflection" tables present
// in generated code with --reflect-types (only types) or --reflect-names
// (also names).
// This allows basic reflection functionality such as pretty-printing
// that does not require the use of the schema parser or loading of binary
// schema files at runtime (reflection.h).

// For any of the functions below that take `const TypeTable *`, you pass
// `FooTypeTable()` if the type of the root is `Foo`.

// First, a generic iterator that can be used by multiple algorithms.

struct IterationVisitor
{
    // These mark the scope of a table or struct.
    virtual void StartSequence() {}
    virtual void EndSequence() {}
    // Called for each field regardless of wether it is present or not.
    // If not present, val == nullptr. set_idx is the index of all set fields.
    virtual void Field(size_t /*field_idx*/, size_t /*set_idx*/, ElementaryType /*type*/, bool /*is_vector*/,
                       const TypeTable* /*type_table*/, const char* /*name*/, const uint8_t* /*val*/)
    {
    }
    // Called for a value that is actually present, after a field, or as part
    // of a vector.
    virtual void UType(uint8_t, const char*) {}
    virtual void Bool(bool) {}
    virtual void Char(int8_t, const char*) {}
    virtual void UChar(uint8_t, const char*) {}
    virtual void Short(int16_t, const char*) {}
    virtual void UShort(uint16_t, const char*) {}
    virtual void Int(int32_t, const char*) {}
    virtual void UInt(uint32_t, const char*) {}
    virtual void Long(int64_t) {}
    virtual void ULong(uint64_t) {}
    virtual void Float(float) {}
    virtual void Double(double) {}
    virtual void String(const String*) {}
    virtual void Unknown(const uint8_t*) {}    // From a future version.
    // These mark the scope of a vector.
    virtual void StartVector() {}
    virtual void EndVector() {}
    virtual void Element(size_t /*i*/, ElementaryType /*type*/, const TypeTable* /*type_table*/, const uint8_t* /*val*/)
    {
    }
    virtual ~IterationVisitor() {}
};

inline size_t InlineSize(ElementaryType type, const TypeTable* type_table)
{
    switch(type)
    {
        case ET_UTYPE:
        case ET_BOOL:
        case ET_CHAR:
        case ET_UCHAR:
            return 1;
        case ET_SHORT:
        case ET_USHORT:
            return 2;
        case ET_INT:
        case ET_UINT:
        case ET_FLOAT:
        case ET_STRING:
            return 4;
        case ET_LONG:
        case ET_ULONG:
        case ET_DOUBLE:
            return 8;
        case ET_SEQUENCE:
            switch(type_table->st)
            {
                case ST_TABLE:
                case ST_UNION:
                    return 4;
                case ST_STRUCT:
                    return type_table->values[type_table->num_elems];
                default:
                    assert(false);
                    return 1;
            }
        default:
            assert(false);
            return 1;
    }
}

inline int32_t LookupEnum(int32_t enum_val, const int32_t* values, size_t num_values)
{
    if(!values)
        return enum_val;
    for(size_t i = 0; i < num_values; i++)
    {
        if(enum_val == values[i])
            return static_cast<int32_t>(i);
    }
    return -1;    // Unknown enum value.
}

template <typename T> const char* EnumName(T tval, const TypeTable* type_table)
{
    if(!type_table || !type_table->names)
        return nullptr;
    auto i = LookupEnum(static_cast<int32_t>(tval), type_table->values, type_table->num_elems);
    if(i >= 0 && i < static_cast<int32_t>(type_table->num_elems))
    {
        return type_table->names[i];
    }
    return nullptr;
}

void IterateObject(const uint8_t* obj, const TypeTable* type_table, IterationVisitor* visitor);

inline void IterateValue(ElementaryType type, const uint8_t* val, const TypeTable* type_table, const uint8_t* prev_val,
                         soffset_t vector_index, IterationVisitor* visitor)
{
    switch(type)
    {
        case ET_UTYPE:
        {
            auto tval = *reinterpret_cast<const uint8_t*>(val);
            visitor->UType(tval, EnumName(tval, type_table));
            break;
        }
        case ET_BOOL:
        {
            visitor->Bool(*reinterpret_cast<const uint8_t*>(val) != 0);
            break;
        }
        case ET_CHAR:
        {
            auto tval = *reinterpret_cast<const int8_t*>(val);
            visitor->Char(tval, EnumName(tval, type_table));
            break;
        }
        case ET_UCHAR:
        {
            auto tval = *reinterpret_cast<const uint8_t*>(val);
            visitor->UChar(tval, EnumName(tval, type_table));
            break;
        }
        case ET_SHORT:
        {
            auto tval = *reinterpret_cast<const int16_t*>(val);
            visitor->Short(tval, EnumName(tval, type_table));
            break;
        }
        case ET_USHORT:
        {
            auto tval = *reinterpret_cast<const uint16_t*>(val);
            visitor->UShort(tval, EnumName(tval, type_table));
            break;
        }
        case ET_INT:
        {
            auto tval = *reinterpret_cast<const int32_t*>(val);
            visitor->Int(tval, EnumName(tval, type_table));
            break;
        }
        case ET_UINT:
        {
            auto tval = *reinterpret_cast<const uint32_t*>(val);
            visitor->UInt(tval, EnumName(tval, type_table));
            break;
        }
        case ET_LONG:
        {
            visitor->Long(*reinterpret_cast<const int64_t*>(val));
            break;
        }
        case ET_ULONG:
        {
            visitor->ULong(*reinterpret_cast<const uint64_t*>(val));
            break;
        }
        case ET_FLOAT:
        {
            visitor->Float(*reinterpret_cast<const float*>(val));
            break;
        }
        case ET_DOUBLE:
        {
            visitor->Double(*reinterpret_cast<const double*>(val));
            break;
        }
        case ET_STRING:
        {
            val += ReadScalar<uoffset_t>(val);
            visitor->String(reinterpret_cast<const String*>(val));
            break;
        }
        case ET_SEQUENCE:
        {
            switch(type_table->st)
            {
                case ST_TABLE:
                    val += ReadScalar<uoffset_t>(val);
                    IterateObject(val, type_table, visitor);
                    break;
                case ST_STRUCT:
                    IterateObject(val, type_table, visitor);
                    break;
                case ST_UNION:
                {
                    val += ReadScalar<uoffset_t>(val);
                    assert(prev_val);
                    auto union_type = *prev_val;    // Always a uint8_t.
                    if(vector_index >= 0)
                    {
                        auto type_vec = reinterpret_cast<const Vector<uint8_t>*>(prev_val);
                        union_type = type_vec->Get(static_cast<uoffset_t>(vector_index));
                    }
                    auto type_code_idx = LookupEnum(union_type, type_table->values, type_table->num_elems);
                    if(type_code_idx >= 0 && type_code_idx < static_cast<int32_t>(type_table->num_elems))
                    {
                        auto type_code = type_table->type_codes[type_code_idx];
                        switch(type_code.base_type)
                        {
                            case ET_SEQUENCE:
                            {
                                auto ref = type_table->type_refs[type_code.sequence_ref]();
                                IterateObject(val, ref, visitor);
                                break;
                            }
                            case ET_STRING:
                                visitor->String(reinterpret_cast<const String*>(val));
                                break;
                            default:
                                visitor->Unknown(val);
                        }
                    }
                    else
                    {
                        visitor->Unknown(val);
                    }
                    break;
                }
                case ST_ENUM:
                    assert(false);
                    break;
            }
            break;
        }
        default:
        {
            visitor->Unknown(val);
            break;
        }
    }
}

inline void IterateObject(const uint8_t* obj, const TypeTable* type_table, IterationVisitor* visitor)
{
    visitor->StartSequence();
    const uint8_t* prev_val = nullptr;
    size_t set_idx = 0;
    for(size_t i = 0; i < type_table->num_elems; i++)
    {
        auto type_code = type_table->type_codes[i];
        auto type = static_cast<ElementaryType>(type_code.base_type);
        auto is_vector = type_code.is_vector != 0;
        auto ref_idx = type_code.sequence_ref;
        const TypeTable* ref = nullptr;
        if(ref_idx >= 0)
        {
            ref = type_table->type_refs[ref_idx]();
        }
        auto name = type_table->names ? type_table->names[i] : nullptr;
        const uint8_t* val = nullptr;
        if(type_table->st == ST_TABLE)
        {
            val = reinterpret_cast<const Table*>(obj)->GetAddressOf(FieldIndexToOffset(static_cast<voffset_t>(i)));
        }
        else
        {
            val = obj + type_table->values[i];
        }
        visitor->Field(i, set_idx, type, is_vector, ref, name, val);
        if(val)
        {
            set_idx++;
            if(is_vector)
            {
                val += ReadScalar<uoffset_t>(val);
                auto vec = reinterpret_cast<const Vector<uint8_t>*>(val);
                visitor->StartVector();
                auto elem_ptr = vec->Data();
                for(size_t j = 0; j < vec->size(); j++)
                {
                    visitor->Element(j, type, ref, elem_ptr);
                    IterateValue(type, elem_ptr, ref, prev_val, static_cast<soffset_t>(j), visitor);
                    elem_ptr += InlineSize(type, ref);
                }
                visitor->EndVector();
            }
            else
            {
                IterateValue(type, val, ref, prev_val, -1, visitor);
            }
        }
        prev_val = val;
    }
    visitor->EndSequence();
}

inline void IterateFlatBuffer(const uint8_t* buffer, const TypeTable* type_table, IterationVisitor* callback)
{
    IterateObject(GetRoot<uint8_t>(buffer), type_table, callback);
}

// Outputting a Flatbuffer to a string. Tries to conform as close to JSON /
// the output generated by idl_gen_text.cpp.

struct ToStringVisitor : public IterationVisitor
{
    std::string s;
    void StartSequence()
    {
        s += "{ ";
    }
    void EndSequence()
    {
        s += " }";
    }
    void Field(size_t /*field_idx*/, size_t set_idx, ElementaryType /*type*/, bool /*is_vector*/,
               const TypeTable* /*type_table*/, const char* name, const uint8_t* val)
    {
        if(!val)
            return;
        if(set_idx)
            s += ", ";
        if(name)
        {
            s += name;
            s += ": ";
        }
    }
    template <typename T> void Named(T x, const char* name)
    {
        if(name)
            s += name;
        else
            s += NumToString(x);
    }
    void UType(uint8_t x, const char* name)
    {
        Named(x, name);
    }
    void Bool(bool x)
    {
        s += x ? "true" : "false";
    }
    void Char(int8_t x, const char* name)
    {
        Named(x, name);
    }
    void UChar(uint8_t x, const char* name)
    {
        Named(x, name);
    }
    void Short(int16_t x, const char* name)
    {
        Named(x, name);
    }
    void UShort(uint16_t x, const char* name)
    {
        Named(x, name);
    }
    void Int(int32_t x, const char* name)
    {
        Named(x, name);
    }
    void UInt(uint32_t x, const char* name)
    {
        Named(x, name);
    }
    void Long(int64_t x)
    {
        s += NumToString(x);
    }
    void ULong(uint64_t x)
    {
        s += NumToString(x);
    }
    void Float(float x)
    {
        s += NumToString(x);
    }
    void Double(double x)
    {
        s += NumToString(x);
    }
    void String(const struct String* str)
    {
        EscapeString(str->c_str(), str->size(), &s, true);
    }
    void Unknown(const uint8_t*)
    {
        s += "(?)";
    }
    void StartVector()
    {
        s += "[ ";
    }
    void EndVector()
    {
        s += " ]";
    }
    void Element(size_t i, ElementaryType /*type*/, const TypeTable* /*type_table*/, const uint8_t* /*val*/)
    {
        if(i)
            s += ", ";
    }
};

inline std::string FlatBufferToString(const uint8_t* buffer, const TypeTable* type_table)
{
    ToStringVisitor tostring_visitor;
    IterateFlatBuffer(buffer, type_table, &tostring_visitor);
    return tostring_visitor.s;
}

}    // namespace flatbuffers

#endif    // FLATBUFFERS_MINIREFLECT_H_
